package org.springrain.mojo;


import com.thoughtworks.qdox.model.JavaClass;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.codehaus.plexus.util.StringUtils;
import org.springrain.javadoc4openapi.JavaDoc4OpenAPI;
import org.springrain.javadoc4openapi.JavaSourceUtils;
import org.springrain.javadoc4openapi.OpenAPIUtils;
import org.yaml.snakeyaml.Yaml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;

/**
 * 基于javadoc4openapi,在项目编译时,生成项目的API文档
 * 使用注释的方式,没有使用注解
 * openapi 对应pom plugin的<goal>openapi</goal> 配置
 * compile 对应pom plugin的<phase>compile</phase>
 * 如果属性不用了,要把属性的注释一块删除,不然会影响他下面的属性
 *
 * @author springrain
 * @version 0.0.1
 * @goal openapi
 * @phase compile
 * @since 0.0.1
 */
public class JavaDoc4OpenAPIMojo extends AbstractMojo {


    /**
     * Maven项目对象
     *
     * @parameter default-value="${project}"
     * @readonly
     * @required
     */
    private MavenProject mavenProject;


    /**
     * Maven项目对象
     *
     * @parameter default-value="${session}"
     * @readonly
     * @required
     */
    private MavenSession mavenSession;


    /**
     * Maven本地仓库的地址,在setting.xml文件中配置
     *
     * @parameter default-value="${settings.localRepository}"
     * @readonly
     * @required
     */
    private File localRepository;


    /**
     * 依赖包的树形结构,获取到所有的依赖jar包
     *
     * @component hint="default"
     * @required
     */
    private DependencyGraphBuilder dependencyGraphBuilder;

    /**
     * 仓库系统,可以自动下载依赖的jar包源码
     *
     * @component
     * @required
     */
    private RepositorySystem repositorySystem;

    /**
     * 项目主代码编译输出目录，默认为target/classes/
     *
     * @parameter default-value="${project.build.outputDirectory}"
     * @required
     */

    private File outputDirectory;
    /**
     * 项目的主源码目录，默认为src/main/java/
     *
     * @parameter default-value="${project.build.sourceDirectory}"
     * @required
     */
    private File sourceDirectory;

    /**
     * 项目的 groupId ,默认只加载相同groupId下的source源码
     *
     * @parameter default-value="${project.groupId}"
     * @required
     */
    private String groupId;

    /**
     * 项目的 artifactId
     *
     * @parameter default-value="${project.artifactId}"
     * @required
     */
    private String artifactId;


    /**
     * 项目的 version
     *
     * @parameter default-value="${project.version}"
     * @required
     */
    private String version;

    /**
     * 需要解析的项目源码,格式为: groupId:artifactId,可以只填写groupId,默认只有本项目的groupId
     *
     * @parameter default-value="${includes}"
     */
    private Set<String> includes;


    /**
     * 插件逻辑入口.
     *
     * @throws MojoExecutionException
     */
    public void execute() throws MojoExecutionException {
        //把本项目的groupId添加进来,默认本项目groupId下的模块源码都需要加载.
        includes.add(groupId);

        try {
            //根据当前的mavenSession 构建编译请求
            ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(mavenSession.getProjectBuildingRequest());
            //设置项目
            buildingRequest.setProject(mavenProject);
            //获取项目的依赖根节点,就是项目本身
            DependencyNode rootGraph = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);

            //项目依赖的节点,第一个是项目本身.
            List<DependencyNode> dependencyNodes = new ArrayList<>();
            dependencyNodes.add(rootGraph);


            //依赖第三方jar的maven坐标,只包含includes范围内的
            List<Artifact> dependencyArtifacts = new ArrayList<>();
            //根据项目的根节点,递归查找所有的第三方依赖,只包含includes范围内的
            findDependencyArtifacts(dependencyNodes, dependencyArtifacts);

            //所有的源码jar包文件,包含第三方依赖的和项目module
            List<File> allSourceJars = new ArrayList<>();

            //遍历获取的第三方依赖,只包含includes范围内的
            for (Artifact dependencyArtifact : dependencyArtifacts) {
                //获取依赖的源码包坐标
                Artifact sourcesArtifact = repositorySystem.createArtifactWithClassifier(dependencyArtifact.getGroupId(), dependencyArtifact.getArtifactId(), dependencyArtifact.getVersion(), dependencyArtifact.getType(), "sources");
                //加载源码包的source.jar包文件
                List<File> sourceJars = loadDependencySourceJars(sourcesArtifact);
                if (CollectionUtils.isEmpty(sourceJars)) {
                    continue;
                }
                allSourceJars.addAll(sourceJars);
            }
            //找到module项目本地install的maven路径,加载source源码包文件
            loadModuleSourceJars(mavenProject, allSourceJars);

            for (File jarFile : allSourceJars) {//遍历源码jar包
                try {
                    List<JavaClass> javaClasses = JavaSourceUtils.loadJarFile(jarFile);
                    Map<String, JavaClass> javaSourceMap = JavaSourceUtils.transforJavaClass(javaClasses);
                    //加载源码包
                    JavaDoc4OpenAPI.javaClassMap.putAll(javaSourceMap);
                } catch (IOException e) {
                    getLog().error(e.getMessage(), e);
                    continue;
                }
            }

            //获取openapi的Map
            Map<String, Object> openAPIMap = JavaDoc4OpenAPI.openAPIMap();

            //项目信息的Map
            Map<String, Object> projectMap = new HashMap<>();
            Map<String, Object> projectInfoMap = new HashMap<>();
            projectMap.put("info", projectInfoMap);
            String title = artifactId;
            if (StringUtils.isNotBlank(mavenProject.getName())) {
                title = mavenProject.getName();
            }
            String description = artifactId;
            if (StringUtils.isNotBlank(mavenProject.getDescription())) {
                description = mavenProject.getDescription();
            }
            projectInfoMap.put("title", title);
            projectInfoMap.put("description", description);
            projectInfoMap.put("version", version);

            //合并项目信息的map
            OpenAPIUtils.mergeConfigMap(openAPIMap, projectMap);


            //读取用户自定义的配置
            try {
                File userYamlConfig = new File("src/main/resources/openapi-config.yaml");
                if (userYamlConfig.exists()) {
                    Yaml yaml = new Yaml();
                    Map userConfigMap = yaml.loadAs(new FileInputStream(userYamlConfig), Map.class);
                    //合并用户配置的map
                    OpenAPIUtils.mergeConfigMap(openAPIMap, userConfigMap);
                }
            } catch (Exception e) {
                getLog().error(e.getMessage(), e);
            }


            //根据map生成openapi的yaml文件
            String yamlFilePath = "src/main/resources/javadoc4openapi.yaml";
            JavaDoc4OpenAPI.makeYamlFileByOpenAPIMap(openAPIMap, yamlFilePath);

        } catch (Exception e) {
            getLog().error(e);
        }


    }


    /**
     * 获取项目依赖的jar,只返回includes配置的jar
     *
     * @param dependencyNodes     依赖的节点
     * @param dependencyArtifacts 接收依赖的坐标,递归往里面放值
     */

    private void findDependencyArtifacts(List<DependencyNode> dependencyNodes, List<Artifact> dependencyArtifacts) {

        if (CollectionUtils.isEmpty(dependencyNodes)) {
            return;
        }

        //遍历依赖节点
        for (DependencyNode dependencyNode : dependencyNodes) {
            //获取节点坐标
            Artifact artifact = dependencyNode.getArtifact();
            String gid = artifact.getGroupId();
            String aid = artifact.getArtifactId();
            String path = gid + ":" + aid;
            if (includes.contains(gid) || includes.contains(path)) {//符合加载条件
                if (!path.equalsIgnoreCase(groupId + ":" + artifactId)) {//不加载自身项目,本项目是直接读取源码文件的.
                    //添加到list中
                    dependencyArtifacts.add(artifact);
                }
                if (dependencyNode.getChildren().size() > 0) {//如果有子节点依赖
                    //递归调用,加载子节点的坐标
                    findDependencyArtifacts(dependencyNode.getChildren(), dependencyArtifacts);
                }
            }

        }
    }


    /**
     * 加载依赖的jar包的源文件
     *
     * @param sourcesArtifact jar源文件的maven坐标对象
     */
    private List<File> loadDependencySourceJars(Artifact sourcesArtifact) {
        //所有的jar文件
        List<File> javaSourceJars = new ArrayList<>();
        // create request
        ArtifactResolutionRequest request = new ArtifactResolutionRequest();
        request.setArtifact(sourcesArtifact);
        //request.setResolveTransitively(true);
        //设置远程仓库,用于下载源码包
        request.setRemoteRepositories(mavenProject.getRemoteArtifactRepositories());
        // resolve deps
        ArtifactResolutionResult result = repositorySystem.resolve(request);
        //获取到请求的坐标
        Set<Artifact> artifacts = result.getArtifacts();
        for (Artifact artifact : artifacts) {
            //获取到源码文件
            File sourceJar = artifact.getFile();
            if (!sourceJar.exists()) {//如果文件不存在
                continue;
            }
            //加载到源码列表
            javaSourceJars.add(sourceJar);
        }
        return javaSourceJars;
    }


    /**
     * 加载项目模块的源码包jar文件
     *
     * @param project
     * @param allSourceJars
     */
    private void loadModuleSourceJars(MavenProject project, List<File> allSourceJars) {

        if (!project.hasParent()) {
            return;
        }
        MavenProject projectParent = project.getParent();

        String parentGroupId = projectParent.getGroupId();
        if (!parentGroupId.equalsIgnoreCase(groupId)) {//不是本项目的group 不处理
            return;
        }
        List<String> moduleList = projectParent.getModules();

        if (!projectParent.hasParent()) {//如果没有父级了,就把自身加载上
            moduleList.add(projectParent.getArtifactId());
        }


        if (CollectionUtils.isEmpty(moduleList)) {
            return;
        }

        //版本
        String version = projectParent.getVersion();
        //本地仓库地址
        String localRepositoryDir = localRepository.getAbsolutePath();

        for (String moduleName : moduleList) {
            if (moduleName.equalsIgnoreCase(artifactId)) {//本项目,不处理
                continue;
            }
            //拼接本地install的路径
            String sourceJarFilePath = localRepositoryDir + "/" + parentGroupId.replaceAll("\\.", "/") + "/" + moduleName + "/" + version + "/" + moduleName + "-" + version + "-sources.jar";


            File sourceJarFile = new File(sourceJarFilePath);
            if (sourceJarFile.exists()) {//如果文件存在
                allSourceJars.add(sourceJarFile);
                //System.out.println("sourceJarFilePath:"+sourceJarFilePath);
            }

        }
        //递归加载父级的源码文件
        loadModuleSourceJars(projectParent, allSourceJars);


    }


}
